Repository navigation
fix(ai): real endpoints, --agent AI mode, claude auth errors, keyless auto-pick, an isolated claude -p - #850
Merged
Merged
Conversation
…part from the user's setup claude -p reports a failure on stdout as a JSON result with is_error: true and leaves stderr empty, so the not-authenticated check (stderr only) never fired and the error read "claude-code exited with code 1:" with no reason. The provider now reads that result: the auth markers are matched against it plus stderr, any other failure carries the agent's own redacted message, and is_error: true fails even at exit code 0. Each claude-code call now replaces Claude Code's agent system prompt (--system-prompt), passes --tools "", --strict-mcp-config, --setting-sources= and --no-session-persistence, and drops CLAUDECODE from the child env, so the user's hooks, plugins, MCP servers and CLAUDE.md no longer reach contract generation. Each flag is used only when the installed claude --help lists it (probed once per process), so older CLIs keep working. Pattern from claude-agent-sdk-python (MIT) @ 3cf3446b0f, subprocess_cli.py _build_command (SDK #778, #732).
…agent when nothing is configured LlmConfig.endpoint carried the telemetry label litellm://<provider>/<model> whenever no endpoint was set. Ollama sent it to litellm as api_base, so no call reached the daemon, and fluid ai test posted to it and failed with UnsupportedProtocol. The endpoint now holds only a real address (the stated one, else OLLAMA_HOST, else litellm's own default). OLLAMA_HOST is read as ollama-python's _parse_host reads it (MIT), routing calls keep a stated endpoint, fluid ai test probes through the provider call and exits 4 on a 404, and doctor shows "Provider default". A non-interactive run with nothing configured now uses a running local Ollama first, else the first coding-agent CLI on PATH (claude-code, codex, cursor, kiro), and logs which. A key, a keyring entry, a saved provider, OLLAMA_HOST, or a stated endpoint or model (flags or env, the routing ones too) blocks it, as does the agent loop. The litellm preflight lets it through on the base install, where the agents are the only provider that can run. Readiness reports an agent ready only when its CLI is on PATH.
…tract it wrote fluid forge --agent forced --blank unless a mode flag was given, so --llm-provider, --llm-model, --llm-endpoint and --context were dropped and the run wrote a TODO contract with exit_code 0, which an IDE agent reads as success. Those flags now select AI mode, run non-interactively, and a failed generation exits non-zero. The receipt path compared diff_snapshots actions against "created"/"modified" while it emits "create"/"update", so AI-mode runs never sent forge.contract_written. The actions are now mapped to the blank path's vocabulary.
📄 Documentation ReminderThis PR appears to be missing a documentation reference. Our docs live in a separate repo. Please update the PR description with one of:
See the Contributing Guide for details. |
…numbers The lean-install preflight test sets GEMINI_API_KEY to a dummy value, which detect-secrets flags as a Secret Keyword. It now carries the inline allowlist pragma the other tests use. The baseline's line numbers for already-audited fixtures in the touched test files moved with this PR's edits; detect-secrets-hook rewrote them.
litellm 1.109.0 (released 2026-11-11) reads fourteen more <PROVIDER>_API_BASE names: ABLITERATION, AIAND, BIGMODEL, CLF_AI_GATEWAY, EU_JEV, HOSTINGER_ROUTER, KYMA, NACE, OPENCODE_GO, OPENINFER, OPENSEARCH, REFUSELESS, STREAMLAKE and ZEROGPU. Each comes from litellm.provider_list, llms/openai_like/providers.json or the provider's own module, so each picks a provider host and joins LITELLM_ENDPOINT_NAMES. tests/test_litellm_endpoint_names.py failed on every CI leg that installs it (Python 3.11 and up; 3.10 stays below 1.98). It passes on litellm 1.109.0 and 1.104.0.
This was referenced Nov 12, 2026
fix(ai): #850 follow-ups — target-dir event, one Ollama reader for any host, no gemini fallback
#852
Merged
fas89
added a commit
that referenced
this pull request
Nov 12, 2026
…y host, no gemini fallback (#852) * fix(forge): --agent reports a contract written to a --target-dir outside the cwd The receipt and the forge.contract_written event come from a before/after snapshot that scanned only the current directory, two levels deep. A --target-dir outside it, or deeper, got neither: an AI-mode run wrote the contract and the JSONL stream never said so. The stated target is now snapshotted too, with ~ expanded as the writer expands it, and a file outside the current directory is reported by its absolute path. Found by the forge_docs review of #850. * fix(ai): one Ollama address for every read, any host, from the stated endpoint OLLAMA_HOST was read four ways. The chat call dialled any host, but detection, forge's default-model pick and fluid ai setup ignored a non-local host with a warning and asked localhost, so the model came from one daemon and the call went to another. fluid ai test --endpoint refused a non-local Ollama outright. Owner decision: any host, as in ollama-python. OLLAMA_HOST is already a protected name that a project .env cannot set. - fluid_build/llm/ollama_host.py is the one reader, following ollama-python's _parse_host convention (MIT). It keeps user:password@, sent as basic auth, and reads the scheme case-insensitively. - A stated endpoint (--llm-endpoint, FLUID_LLM_ENDPOINT, ai test --endpoint) now drives the default-model pick, the model preflight and the ai test model check, which asked OLLAMA_HOST or localhost. A --tiered stage keeps a stated endpoint, as LlmConfig.for_routing already did. - LlmProvider.list_available_models takes an endpoint keyword, passed only when one is stated. The Ollama model list moved from urllib to httpx, so basic auth works and no redirect is followed; a non-http(s) URL is refused. - Credentials: fluid doctor and fluid ai setup show the address masked (providers.redact_endpoint), ai_config.json stores it without them unless FLUID_ALLOW_PLAINTEXT_AI_SECRETS is set, and the environment's OLLAMA_HOST wins over the saved host. * fix(ai): stop with copilot_no_llm_provider instead of guessing gemini With nothing selecting a provider, forge used gemini. It sent FLUID_LLM_API_KEY to Google whatever key it was, and with two provider keys set it asked for a gemini key, while fluid doctor said "Not selected". Owner decision: no silent fallback. resolve_llm_config now raises copilot_no_llm_provider before any provider is built, with no_provider_message(env), shared with check_llm_readiness so forge and doctor say the same thing: nothing configured, several keys set, a key that names no provider, or a saved fluid ai setup choice the command does not read. The error catalog carries the suggestions. * test(ai): reset the setup-skip flag in the inline-setup credentials test Coverage & Audit (Python 3.14) failed on test_inline_setup_keeps_the_credentials_in_ollama_host with "'NoneType' object has no attribute 'provider'": run_ai_setup_inline returns None when an earlier test on the same worker left the module-level _ai_setup_skipped set, and random test order put one first. The test now resets the flag, as the other inline-setup tests do, points HOME at its tmp dir and pins the litellm check, so it reads nothing of the machine it runs on.
This was referenced Nov 12, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A review of forge's AI authoring path (2026-11-06) found five defects, all reproduced on
origin/main:LlmConfig.endpointheld the telemetry labellitellm://<provider>/<model>whenever no endpoint was set.api_base(litellm://ollama/gemma3:4b), so no local call ever reached the daemon.fluid ai testposted to it and failed with "AI test network error for ollama (UnsupportedProtocol)".fluid forge --agentforced--blankunless a mode flag was given.--agent --llm-provider claude-code --context '<intent>'wrotedescription: 'TODO: Describe your data product'/sql: SELECT 1 AS placeholderand emittedforge.done exit_code 0, which an IDE agent reads as success.claude -p --output-format jsonreports a failure on stdout (is_error: true, e.g. "Failed to authenticate: OAuth session expired and could not be refreshed") and leaves stderr empty. The not-authenticated check read stderr only, so users saw "claude-code exited with code 1:" and nothing else.geminieven when a logged-in Claude Code was installed.claude -pran with Claude Code's full agent prompt plus the user's own hooks, plugins, MCP servers and CLAUDE.md: 47,258 prompt tokens for a one-line call.Founder decisions:
--agenthonors the AI flags.--llm-model.The change
8fa6a051fix(coding-agent) (3, 5): failures are read from the agent's stdout result.is_error: truefails even at exit code 0, and a login problem raisescoding_agent_not_authenticatedwith the sign-in hint. Forclaude-codeonly:--system-prompt;--tools "",--strict-mcp-config,--setting-sources=(one token, SDK fix(databricks): a contract's sovereignty policy holds for a Databricks expose #778) and--no-session-persistenceare added;CLAUDECODEis dropped from the child env (SDK feat(loader): overlays patch builds, exposes and consumes by identity (schema 0.7.6) #732);claude --helplists it (probed once per process).Pattern from claude-agent-sdk-python (MIT) @ 3cf3446b0f,
subprocess_cli.py::_build_command.f2aac8edfix(llm) (1, 4):LlmConfig.endpointholds only a real address: the stated one, elseOLLAMA_HOST, else litellm's own default.OLLAMA_HOSTis parsed the way ollama-python's_parse_host(MIT) parses it: a bare host means:11434.endpoint_explicit).fluid ai testprobes through the provider call and exits 4 on a 404.FLUID_LLM_[ROUTING_]ENDPOINT/MODEL,OLLAMA_HOST, a keyring key, a saved provider,--llm-endpoint/--llm-model(and the routing twins), or the agent loop (flag orFLUID_COPILOT_AGENT_LOOP).331f6803fix(forge) (2):--llm-provider/--llm-model/--llm-endpoint/--contextselect AI mode under--agent, run non-interactively. AI-mode runs now emitforge.contract_written: the receipt path compareddiff_snapshots'create/updateagainstcreated/modified, so it never fired.fadbdbf5: CHANGELOG[Unreleased](Fixed, Changed).Review
Borrow-before-build:
subprocess_cli.py+ CHANGELOG;llms/ollama/common_utils.py);_parse_host.Pattern adapted, no new dependency.
Security review (diff-scoped), 1 finding, fixed and re-checked: a stated
--llm-endpoint/--llm-model/FLUID_LLM_*or provider key did not count as configured. A non-interactive run,--agent --llm-endpoint http://onprem-llm.internal:8000/v1included, therefore sent the prompt toclaude-code. The routing endpoint/model variant is closed too.Defect scan (diff-scoped, reproduced), 3 P2 + 3 P3, all fixed and re-checked:
--agent-loopauto-picking an agent that has no tool use;for_routing()dropping a stated Ollama endpoint;OLLAMA_HOSTpassing the lean preflight;ai test404 exiting 5.Accepted residual: on the base install, a keyring-only key passes the preflight (which reads no keyring by design) and fails cleanly at resolve time.
Testing
tests/test_llm_endpoint_sentinel.py,tests/test_llm_keyless_autopick.py,tests/test_coding_agent_isolation.py, plus additions intest_litellm_absent.py,cli/test_forge_agent_mode.pyandtest_coding_agent_provider.py.tests/cli/test_lazy_core_commands.py) fail the same way on pristinemainin a shared venv.fluid --helploads the same 1028 modules onmainand on the branch, with none of litellm/httpx/mcp/jsonschema.fluid ai test --provider ollama --model qwen3-coder:30b:mainok: false(UnsupportedProtocol); branchok: true(FLUID_OK) withOLLAMA_HOSTunset,localhostand127.0.0.1:11434.fluid forge --agent --llm-provider ollama --llm-model qwen3-coder:30b --context <intent>: modeai, valid contract, 107 s.fluid forge --agent --llm-provider claude-code --llm-model sonnet --context <intent>: modeai,forge.contract_written, a valid contract scoring 10/12 on a fixed fidelity checklist, 57 s, 2 calls, $0.17.claude -p --json-schemawith the isolation flags still returnsstructured_output. The same call went from 47,258 to 1,301 prompt tokens and from 4.2 s to 1.3 s.fluid forge data-model from-intent ... --llm-provider {openai|anthropic|claude|gemini}, with the key in env and no--llm-model, resolves the same provider, model and routing model asmain. Only the endpoint changes, fromlitellm://…to"".Documentation
fluid forge --agent(AI flags select AI mode,forge.contract_writtenin AI mode), keyless auto-pick for non-interactive runs,claude -pisolation and settings-based Claude auth,fluid ai testexit codes,fluid doctor"Provider default".Not in this PR
--llm-model.fluid ai setupprovider, and--agentstdout mixes rich panels into the JSONL stream.